// 版权所有2016 etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package concurrency

import (
	"context"
	"time"

	v3 "go.etcd.io/etcd/client/v3"
)

const defaultSessionTTL = 60

// 会话表示在客户端生命周期内保持活动状态的租约。
// 容错应用程序可能会使用会话来推断活动性。
type Session struct {
	client *v3.Client
	opts   *sessionOptions
	id     v3.LeaseID

	cancel context.CancelFunc
	donec  <-chan struct{}
}

// NewSession获取客户端的租用会话。
func NewSession(client *v3.Client, opts ...SessionOption) (*Session, error) {
	ops := &sessionOptions{ttl: defaultSessionTTL, ctx: client.Ctx()}
	for _, opt := range opts {
		opt(ops)
	}

	id := ops.leaseID
	if id == v3.NoLease {
		resp, err := client.Grant(ops.ctx, int64(ops.ttl))
		if err != nil {
			return nil, err
		}
		id = resp.ID
	}

	ctx, cancel := context.WithCancel(ops.ctx)
	keepAlive, err := client.KeepAlive(ctx, id)
	if err != nil || keepAlive == nil {
		cancel()
		return nil, err
	}

	donec := make(chan struct{})
	s := &Session{client: client, opts: ops, id: id, cancel: cancel, donec: donec}

	// 在客户端出错或取消上下文之前保持租约活动
	go func() {
		defer close(donec)
		for range keepAlive {
			// 在保持活动状态通道关闭之前吃掉消息
		}
	}()

	return s, nil
}

// 客户端是连接到会话的etcd客户端。
func (s *Session) Client() *v3.Client {
	return s.client
}

// Lease是绑定到会话的密钥的租约ID。
func (s *Session) Lease() v3.LeaseID { return s.id }

// 完成返回一个通道，该通道在租约孤立、到期或
// 不再刷新时关闭。
func (s *Session) Done() <-chan struct{} { return s.donec }

// 孤立结束会话租约的刷新。如果客户端连接的状态不确定（撤销
// 将失败），或者在转让租赁所有权时，这非常有用。
func (s *Session) Orphan() {
	s.cancel()
	<-s.donec
}

// 关闭会话并撤消会话租约。
func (s *Session) Close() error {
	s.Orphan()
	// 如果revoke花费的时间长于ttl，则租约仍将过期
	ctx, cancel := context.WithTimeout(s.opts.ctx, time.Duration(s.opts.ttl)*time.Second)
	_, err := s.client.Revoke(ctx, s.id)
	cancel()
	return err
}

type sessionOptions struct {
	ttl     int
	leaseID v3.LeaseID
	ctx     context.Context
}

// 会话选项配置会话。
type SessionOption func(*sessionOptions)

// WithTTL以秒为单位配置会话的TTL。
// 如果TTL<=0，将使用默认的60秒TTL。
func WithTTL(ttl int) SessionOption {
	return func(so *sessionOptions) {
		if ttl > 0 {
			so.ttl = ttl
		}
	}
}

// WithLease指定要用于会话的现有leaseID。
// 这在流程重启场景中非常有用，例如，在重启之前从选举中重新获得领导权。
func WithLease(leaseID v3.LeaseID) SessionOption {
	return func(so *sessionOptions) {
		so.leaseID = leaseID
	}
}

// WithContext使用客户端上下文为会话分配上下文，而不是默认为
// 。这对于取消NewSession和
// 立即关闭操作非常有用，无需关闭客户端。如果在Close（）完成之前取消了
// 上下文，则会话的租约将被放弃，并等待到期，而不是被撤销。
func WithContext(ctx context.Context) SessionOption {
	return func(so *sessionOptions) {
		so.ctx = ctx
	}
}
